/***********************************************************************************
 * 文件名： thread_com.c
 * 作者： 刘言
 * 版本： 1
 * 说明：
 * 		单线通信，通信总线只有1根数据线，通信双方各有1个IO口连接到总线上，总线外部通过1个电
 * 阻下拉，空闲时为低电平。
 * 		ThreadCom 通信接口更多细节参考《ThreadCom通信接口》文档。
 * 		本文件提供了以下接口：
 * 		TC_Start:  开始通信
 * 		TC_Stop: 结束通信
 * 		TC_Send:  发送一个数据包
 * 		TC_Receive:  接收一个数据包
 * 		使用流程: 先调用 TC_Start 如果成功说明已经与从机建立通信，
 * 	之后调用 TC_Send TC_Receive 发送或者接收数据，最后调用 TC_Stop 结束本次通信。
 * 修改记录：
 * 	2020/6/1: 初版。 刘言。
***********************************************************************************/
#include "polsys.h"
#include "board.h"
#include "thread_com.h"

// 发送时的半位周期，从机模式时根据接收波特率计算新值
static idata u16 mSendCycle = DEFAULT_HALF_CYCLE;    		


static void Send_1()
{
	TC_OUT_H;
	TC_DELAY_US(mSendCycle);
	TC_OUT_L;
	TC_DELAY_US(mSendCycle);
}

static void Send_0()
{
	TC_OUT_L;
	TC_DELAY_US(mSendCycle);
	TC_OUT_H;
	TC_DELAY_US(mSendCycle);
}

// 发送一个字节，多个字节连续发送的间隔时间需要把控好，既要保证从机已经准备好接收了，也要避免从机超时退出
static void TC_SendByte(u8 dat)
{
	u8 i;
    
	TC_OUT_OUTPP;	//此时输出低电平
	// 发送起始位
	Send_1();

	for(i = 0; i < 8; i++)
	{
		if(dat & 0x80)
		{	// 发送1
			Send_1();
		}
		else
		{	// 发送0
			Send_0();
		}
		dat = dat << 1;
	}
    
    // 发送结束位
    Send_1();
		
	TC_OUT_INHZ;	//释放总线
}

// 接收一个字节，多个字节必须间隔尽量短的时间连续接收，在从机发送之前准备好接收
static tc_errcode_t TC_ReceiveByte(u8 *byte)
{
	bit first=0,last_level=0,edge_level=0,level=0;
	u8 byte_buff=0,now_bit=1;
	u16 i=0,cycle;
	
	for(i=0;i<25000;i++)
	{
		TC_DELAY_US(LEVEL_DETECT_INTTERVAL);	//每隔10us检测一次IO状态
		if(last_level == TC_IN_LEVEL) level = TC_IN_LEVEL;	//连续2次检测到的电平不变，认为有效电平
		last_level = TC_IN_LEVEL;
		
		// 边缘判断
		if(edge_level == 1 && level == 0)   // 下降沿
		{
			edge_level = level;
			if(first == 0)// 第一个下降沿
			{
				first = 1;
				cycle = i + (i/2);	// 计算 3/4 周期
	#if (COMM_ROLE==1)
				mSendCycle = i * LEVEL_DETECT_INTTERVAL + LEVEL_DETECT_INTTERVAL;			//
	#endif
				i = 0;    
			}
			else
			{
				if(i > cycle)  // 收到一个位 = 1
				{
					i = 0;
					now_bit++;
					if(now_bit < 10)
					{
						byte_buff=byte_buff<<1;
						byte_buff|=0x01;
					}
				}
			}
		}
		else if(edge_level == 0 && level == 1)	// 上升沿
		{
			edge_level = level;
			if(first == 0)		// 第一个上升沿
			{
				i = 0;
			}
			else
			{
				if(i > cycle)  // 收到一个位 = 0
				{
					i = 0;
					now_bit++;
					if(now_bit > 9) //==10,	第10个位是0，错误。第10个位是停止位，必须为1。
					{
						return TC_BUS_ERROR;
					}
					byte_buff = byte_buff<<1;
				}
			}
		}

		if(now_bit==10)	//接收完毕了
		{
			*byte = byte_buff;
			return TC_SUCCESS;
		}
	}
	return TC_RX_TIMEOUT;
}

#if (COMM_ROLE==0)
// 开始，建立同步
tc_errcode_t TC_Start()	
{
	u16 i;
	
	TC_REQUEST;
	TC_ENTER_CRI;
	TC_INIT;
	TC_OUT_OUTPP;
	TC_OUT_H;			//输出高电平
	TC_DELAY_US(500);	//持续500us
	TC_OUT_L;			//输出低电平，目的是加速拉低
	TC_DELAY_US(5);		
	TC_OUT_INHZ;		//释放总线
	i=0;
	while(TC_IN_LEVEL)	//等待下降沿，确保已经拉低
	{
		TC_DELAY_US(1);
		i++;
		if(i>120)return TC_BUS_ERROR;
	}
	i=0;
	while(!TC_IN_LEVEL)	//等待上升沿，从机拉高
	{
		TC_DELAY_US(1);
		i++;
		if(i>120)return TC_NO_SLAVE;
	}
	//检测到上升沿，说明存在从机
	i=0;
	while(TC_IN_LEVEL)	//等待下降沿，从机释放
	{
		TC_DELAY_US(1);
		i++;
		if(i>480)return TC_BREAK_SLAVE;
	}
	//检测到下降沿，同步成功
	return TC_SUCCESS;
}
#endif

#if (COMM_ROLE==1)
// 与主机建立同步
tc_errcode_t TC_Start()
{
	u16 i;

	TC_REQUEST;
	TC_ENTER_CRI;
	TC_INIT;

	i=0;
	while(!TC_IN_LEVEL)	//等待主机设置总线
	{
		TC_DELAY_US(100);
		i++;
		if(i>60000)return TC_NO_HOST;
	}
	// 检测到了高电平
	// 等待主机释放总线
	i=0;
	while(TC_IN_LEVEL)
	{
		TC_DELAY_US(1);
		i++;
		if(i>960)return TC_BREAK_HOST;
	}
	// 检测到了低电平，延时30~120us后设置总线
	TC_DELAY_US(45);
	TC_OUT_OUTPP;
	TC_OUT_H;		//输出180us高电平
	TC_DELAY_US(180);
	TC_OUT_INHZ;	//释放总线，外部拉低
	// 等待成为低电平
	i=0;
	while(TC_IN_LEVEL)	
	{
		TC_DELAY_US(1);
		i++;
		if(i>300)return TC_BUS_ERROR;
	}
	// 已被外部电阻拉低，同步成功
	return TC_SUCCESS;
}
#endif

// 结束通信
void TC_Stop()
{
	TC_DEINIT;
	TC_EXIT_CRI;
	TC_RELEASE;
}


// 以下为数据包格式框架的实现，可以考虑独立成一个文件,且与PTC的合并

// 接收数据。有隐患：当长度错误后可能会内存泄露！
// buff: 数据存放地址,不包含长度和校验
// len: 长度存放地址，1字节，长度为数据区长度（buff的长度，不包括长度本身和校验）
// 注意：限定了数据包的格式为 |长度|--- 数据 ---|校验| ，长度为2字节，高字节始终为0，校验为1字节。
// 内部已经进行了数据包校验，即便校验错误， buff 中的数据也已经被填充，*len也已经被修改。
// 可自行决定校验错误的数据如何处理。
tc_errcode_t TC_Receive(u8 *buff,u8 *len)
{
	tc_errcode_t ret;
	u8 i;
	u8 check_code;
	u8 length;
	
	ret = TC_ReceiveByte(&length);	//接收长度 高字节 (0)
	if(ret!=TC_SUCCESS) return ret;
	ret = TC_ReceiveByte(&length);	//接收长度 低字节
	if(ret!=TC_SUCCESS) return ret;
	
	for(i=0;i<length;i++)	//接收数据
	{
		ret = TC_ReceiveByte(&buff[i]);
		if(ret!=TC_SUCCESS) return ret;
	}
	
	ret = TC_ReceiveByte(&check_code);	// 接收校验码
	if(ret != TC_SUCCESS) return ret;

	*len = length;
	
	if(GetSum_U8(buff,length) != check_code)return TC_CHK_ERROR;
	
	return TC_SUCCESS;
}

// 发送一串数据
// buff: 待发送数据缓存（不包括长度和校验）
// length: 数据长度
// 注意：实际发出的数据会被加入长度和校验信息。
// 也就是按照数据包格式： |长度|--- 数据 ---|校验| 发送，长度为2字节，高字节始终为0，校验为1字节。
void TC_Send(u8 * buff,u8 length)
{
	u8 i;
	u8 sum;
	
	TC_DELAY_US(mSendCycle);
	
	sum = GetSum_U8(buff,length);
	TC_SendByte(0x00);
	TC_SendByte(length);
	for(i=0;i<length;i++)
	{
		TC_SendByte(buff[i]);
	}
	TC_SendByte(sum);
}








